from abc import abstractmethod
from collections.abc import Mapping, Container
from typing import Literal, Set, Iterable, Union


class Mapable:

    @abstractmethod
    def to_map(self) -> Mapping:
        pass


class Symbol(Mapable):

    def name(self):
        return self.__class__.__name__

    @abstractmethod
    def value(self):
        pass

    @abstractmethod
    def verify(self, arg):
        pass

    def __eq__(self, other):
        if not isinstance(other, Symbol):
            return
        return other.name() == self.name() and other.value() == self.value()

    def __hash__(self):
        return hash(self.name())

    def to_map(self) -> Mapping:
        _d = dict()
        _d.update({self.name(): self.value()})
        return _d

    def __str__(self):
        return str(self.to_map())

    __repr__ = __str__


class Gt(Symbol):

    def __init__(self, v):
        if not isinstance(v, (int, float)):
            raise TypeError("symbol require field type error")
        self._v = v

    def value(self):
        return self._v

    def verify(self, arg):
        if not isinstance(arg, (int, float)):
            return
        if self._v is None:
            return
        return arg > self._v


class Gte(Symbol):

    def __init__(self, v):
        if not isinstance(v, (int, float)):
            raise TypeError("symbol require field type error")
        self._v = v

    def value(self):
        return self._v

    def verify(self, arg):
        if not isinstance(arg, (int, float)):
            return
        if self._v is None:
            return
        return arg >= self._v


class Lt(Symbol):

    def __init__(self, v):
        if not isinstance(v, (int, float)):
            raise TypeError("symbol require field type error")
        self._v = v

    def value(self):
        return self._v

    def verify(self, arg):
        if not isinstance(arg, (int, float)):
            return
        if self._v is None:
            return
        return arg < self._v


class Lte(Symbol):

    def __init__(self, v):
        if not isinstance(v, (int, float)):
            raise TypeError("symbol require field type error")
        self._v = v

    def value(self):
        return self._v

    def verify(self, arg):
        if not isinstance(arg, (int, float)):
            return
        if self._v is None:
            return
        return arg <= self._v


class Eq(Symbol):

    def __init__(self, v):
        if not isinstance(v, (int, float, str, bool)):
            raise TypeError("symbol require field type error")
        self._v = v

    def value(self):
        return self._v

    def verify(self, arg):
        if not isinstance(arg, (int, float, str, bool)):
            return
        if self._v is None:
            return
        return self._v == arg


class In(Symbol):

    def __init__(self, v):
        if not isinstance(v, Container):
            raise TypeError("symbol require field type error")
        self._v = v

    def value(self):
        return self._v

    def verify(self, arg):
        if not isinstance(arg, (int, float, str, bool)):
            return
        if self._v is None:
            return
        return arg in self._v


Ref = Literal["&", "|"]


class Condition(Mapable):
    def __init__(self, tag: str, symbol: Symbol):
        self._tag = tag
        self._symbol = symbol

    def tag(self) -> str:
        return self._tag

    def symbol(self) -> Symbol:
        return self._symbol

    def to_map(self) -> Mapping:
        _dict = dict()
        _dict.update({"tag": self._tag})
        _dict.update(self._symbol.to_map())
        return _dict

    @classmethod
    def from_map(cls, _map):
        if not isinstance(_map, dict):
            raise TypeError("_map must dict")
        if "tag" not in _map:
            raise ValueError("can`t find field tag")
        if not isinstance("tag", str):
            raise TypeError("tag must str")
        symbol = (set([ss.__name__ for ss in symbols]) & set(_map.keys())).pop()
        if not symbol:
            raise ValueError("can`t find symbol at " + _map["tag"])

        symbol_obj = [s for s in symbols if symbol == s.__name__][0](_map[symbol])
        return cls(_map["tag"], symbol_obj)

    def __eq__(self, other):
        return self._tag == other.tag() and other.symbol() == self._symbol

    def __hash__(self):
        return hash(self._tag)

    def __str__(self):
        return str(self.to_map())

    __repr__ = __str__


symbols = [
    Gt,
    Gte,
    Lt,
    Lte,
    Eq,
    In
]
refs = [
    "&",
    "|"
]


class Rule(Mapable):
    def __init__(self, _id: Union[Iterable[str], str], ref: Ref):
        ids = []
        if isinstance(_id, str):
            ids.append(_id)
        elif isinstance(_id, Iterable):
            for i in _id:
                if not isinstance(i, str):
                    raise TypeError("id must str")
                ids.append(i)
        self._ids = set(ids)
        if ref != "&" and ref != "|":
            ref = "&"
        self._ref = ref
        self._conditions = set()

    # 一个配置可以包含多个ID
    def ids(self) -> Set[str]:
        return self._ids

    # 用来描述条件之间的关系
    def ref(self) -> Ref:
        return self._ref

    def add_condition(self, condition: Condition):
        if not isinstance(condition, Condition):
            raise TypeError("condition must Condition")
        self._conditions.add(condition)

    def remove_condition(self, condition: Condition):
        cc = None
        for c in self._conditions:
            if condition.tag() == c.tag():
                cc = c
        if cc:
            self._conditions.remove(cc)

    def conditions(self) -> Set[Condition]:
        return self._conditions

    @classmethod
    def from_map(cls, _map):
        if not isinstance(_map, dict):
            raise TypeError("_map must dict")
        if "id" not in _map:
            raise ValueError("can`t find field id")
        obj = cls(_map['id'], _map.get("ref"))
        if "conditions" in _map and isinstance(_map["conditions"], Iterable):
            for c in _map["conditions"]:
                obj.add_condition(Condition.from_map(c))
        return obj

    def to_map(self) -> Mapping:
        _dict = dict()
        _dict.update({"id": list(self._ids), "ref": self._ref, "conditions": [c.to_map() for c in self._conditions]})
        return _dict

    def __and__(self, other):
        if self._ids & other.ids() or "*" in self._ids:
            cs = self._conditions & other.conditions()
            for c in cs:
                other.remove_condition(c)

        if other.conditions():
            return other

    def __str__(self):
        return str(self.to_map())

    __repr__ = __str__


def mix(*conf: Iterable[Rule]):
    # find *
    conf = list(conf)
    root = None
    index = -1
    for i, c in enumerate(conf):
        if "*" in c.ids():
            root = c
            index = i
            break
    if root is not None:
        conf.pop(index)
        others = conf
    else:
        root, *others = conf
    mixed = [root]
    for c in others:
        for m in mixed:
            if isinstance(c, Rule) and isinstance(m, Rule):
                # 去重
                cc = m & c
                if cc is None:
                    break
                else:
                    c = cc
        if c.conditions():
            mixed.append(c)
    return mixed
